// XOR Crypter decoder
// CC-BY: hasherezade
// for Malwarebytes

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#ifdef _MSC_VER
    #include <stdint.h>
#else
    #include <inttypes.h>
#endif

#ifndef BYTE
    typedef unsigned char BYTE;
#endif

#ifndef WORD
   typedef uint16_t WORD; 
#endif

#ifndef DWORD
   typedef uint32_t DWORD; 
#endif

    const BYTE EXPECTED_OUTPUT[] = "GetProcAddress\x0\x0\x0"
"VirtualAlloc\x0\x0\x0\x0\x0"
"VirtualFree\x0\x0\x0\x0\x0\x0"
"UnmapViewOfFile\x0\x0"
"VirtualProtect\x0\x0\x0"
"LoadLibraryExA\x0\x0\x0"
"GetModuleHandleA\x0"
"CreateFileA\x0\x0\x0\x0\x0\x0"
"SetFilePointer\x0\x0\x0"
"WriteFile\x0\x0\x0\x0\x0\x0\x0\x0"
"CloseHandle\x0\x0\x0\x0\x0\x0"
"GetTempPathA\x0\x0\x0\x0\x0"
"lstrlenA\x0\x0\x0\x0\x0\x0\x0\x0\x0"
"lstrcatA\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0";

bool decode(DWORD *inbuf, //encrypted input
    DWORD *outbuf, //buffer to store the output
    size_t bufsize, 
    const DWORD key, 
    const size_t max_size = SIZE_MAX
    )
{
    if (inbuf == NULL || outbuf == NULL) return false;

    for (size_t i = 0; i < bufsize; i++) {
        DWORD val = inbuf[i];
        DWORD step = i * sizeof(DWORD);
        if (step >= max_size) {
            outbuf[i] = val;
            continue;
        }
        outbuf[i] = (val + step) ^ (key + step);
    }
    return true;
}

bool dump_to_file(BYTE *outbuf, size_t psize, const char *outfile_name)
{
    FILE *outfile = fopen(outfile_name, "wb");
    if (outfile == NULL) {
        printf ("Opening output file failed!\n");
        return false;
    }
    fwrite(outbuf, 1, psize, outfile);
    fclose(outfile);
    printf("Saved output as: %s\n", outfile_name);
    return true;
}

DWORD find_stage1_key(DWORD *inbuf, //encrypted input
    size_t bufsize
)
{
    DWORD *out = (DWORD*) EXPECTED_OUTPUT;
    size_t out_size = sizeof(EXPECTED_OUTPUT) / sizeof(DWORD);

    if (inbuf == NULL || bufsize < out_size) return 0;
    
    DWORD key = inbuf[0] ^ out[0];
    //test key:
    DWORD step = sizeof(DWORD);
    DWORD key_res = (inbuf[1] + step) ^ (key + step);
    if (key_res != out[1]) {
        return 0;
    }
    return key;
}

size_t find_patterns(BYTE *fbuf, size_t fsize, WORD* patterns, WORD anti_pattern)
{
    BYTE *buf = fbuf;
    size_t bufsize = fsize;
    BYTE *ptr1 = NULL;
    size_t offset = 0;

    while ((buf + sizeof(patterns)) < (fbuf + fsize)) {
        ptr1 = (BYTE*) memchr((void*)buf, (int) patterns[0], bufsize);
        if (ptr1 == NULL) {
            return -1;
        }
        WORD *wPtr = (WORD*) ptr1;
        if ((wPtr[2] == patterns[1] && wPtr[4] == patterns[2]) &&
            (wPtr[1] != anti_pattern))
        {
            offset = (BYTE*)ptr1 - fbuf;
            //printf("Got the pattern at: %#0x\n", offset);
            break;
        }
        buf = ptr1 + sizeof(WORD);
        size_t offset = (BYTE*)ptr1 - fbuf;
        bufsize = fsize - offset;
    }
    offset = (BYTE*)ptr1 - fbuf - (sizeof(WORD));
    return offset;
}

size_t find_chunk_beginning(BYTE *fbuf, size_t fsize, size_t offset)
{
    WORD *eWords = (WORD*) EXPECTED_OUTPUT;
    size_t wSize = offset / sizeof(WORD);
    
    WORD patterns[] = {0,0,0};
    patterns[0] = eWords[wSize + 1];
    patterns[1] = eWords[wSize + 3];
    patterns[2] = eWords[wSize + 5];
    
    WORD anti_pattern = eWords[wSize + 2];
    return find_patterns(fbuf,fsize, patterns, anti_pattern);
}

size_t find_chunk_size(BYTE *buf, size_t bufsize, DWORD key)
{
    const size_t out_size = sizeof(EXPECTED_OUTPUT);
    BYTE fragment[out_size];

    memset(fragment, 0, out_size + 1);
    if (!decode((DWORD*)(buf), (DWORD*)(fragment), out_size / sizeof(DWORD), key))
        return -1;

    for (size_t i = 0; i < out_size; i++) {
        if (fragment[i] !=  EXPECTED_OUTPUT[i]) {
            printf("Chunk size = %x\n", i);
            return i;
        }
    }
    return out_size;
}

size_t find_encrypted(BYTE *fbuf, size_t fsize, BYTE* outbuf)
{
    size_t offset1 = find_chunk_beginning(fbuf, fsize, 0);
    if (offset1 == -1) return -1;

    DWORD key = find_stage1_key((DWORD*)(fbuf + offset1), (fsize - offset1) / sizeof(DWORD));
    size_t chunk_size = find_chunk_size(fbuf + offset1, fsize - offset1, key);
    size_t dif = 0;
    if (chunk_size != sizeof(EXPECTED_OUTPUT)) {
        size_t offset2 = find_chunk_beginning(fbuf, fsize, chunk_size);
        if (offset2 == -1 || offset2 < offset1) {
            return -1;
        }
        dif = (offset2 - offset1) - chunk_size;
    }
    
    BYTE *chunk_buf = fbuf + offset1;
    size_t next_offset = offset1;
    size_t out_offset = 0;

    while (next_offset + chunk_size < fsize) {
        memcpy(outbuf + out_offset, fbuf + next_offset, chunk_size);
        next_offset += (chunk_size + dif);
        out_offset += chunk_size;
    }
    return offset1;
}

bool decode_stage1(BYTE *inbuf, BYTE *outbuf, size_t fsize)
{
    //prepare for stage#1 decoding:
    size_t dwSize = fsize / sizeof(DWORD);
    DWORD* dwPtr = (DWORD*) inbuf;

    const DWORD key = find_stage1_key(dwPtr, dwSize);
    if (key == 0) {
        printf ("ERROR: Key not found!\n");
        return false;
    }
    printf ("Stage#1 key: %#02x\n", key);

    //stage#1:
    if (!decode(dwPtr, (DWORD*)outbuf, dwSize, key)) {
        printf ("ERROR: Decoding failed!\n");
        return false;
    }
    return true;
}

bool decode_stage2(BYTE *inbuf, BYTE *outbuf, size_t fsize)
{
    size_t dwSize = fsize / sizeof(DWORD);
    DWORD* dwPtr = (DWORD*) inbuf;

    const DWORD key = 0x03E9;

    //stage#2:
    if (!decode(dwPtr, (DWORD*)outbuf, dwSize, key)) {
        printf ("Decoding failed!\n");
        return false;
    }
    return true;
}

bool is_header_valid(BYTE *buf, size_t buf_size)
{
    if (buf == NULL || buf_size < 2) {
        return false;
    }
    if (buf[0] == 'M' && buf[1] == 'Z') {
        return true;
    }
    return false;
}

DWORD read_payload_size(BYTE *buf, size_t fsize, const size_t psize_offset, const size_t payload_offset)
{
    DWORD psize = *((DWORD*)(buf + psize_offset));
    printf("Found payload size = %#02x = %d\n", psize, psize);

    if (psize > (fsize - payload_offset)) {
        printf("ERROR: invalid payload size!");
        return 0;
    }
    return psize;
}

int main(int argc, char *argv[])
{
    if (argc < 3) {
        printf ("Expected parameters <input file name> <output file name>\n");
        return -1;
    }

    FILE *p_file = fopen(argv[1], "rb");
    fseek(p_file, 0, SEEK_END);
    const size_t fsize = ftell(p_file);
    fseek(p_file, 0, SEEK_SET);

    BYTE* inbuf = (BYTE*) calloc(fsize, sizeof(BYTE));
    BYTE* outbuf = (BYTE*) calloc(fsize, sizeof(BYTE));
    
    fread(inbuf, 1, fsize, p_file);
    fclose(p_file);
    p_file = NULL;

    //check:
    size_t offset = find_encrypted(inbuf, fsize, outbuf);
    if (offset == -1) {
        printf("ERROR: Pattern not found! Probably not packed by this crypter!\n");
        return -1;
    }
    memcpy(inbuf, outbuf, fsize);
    memset(outbuf, 0, fsize);

    if (!decode_stage1(inbuf, outbuf, fsize)) {
        free(inbuf);
        free(outbuf);
        return -1;
    }

    //prepare for stage#2 decoding:
    memset(inbuf, 0, fsize);

    const size_t psize_offset = sizeof(EXPECTED_OUTPUT);
    const size_t payload_offset = psize_offset + sizeof(DWORD);

    DWORD psize = read_payload_size(outbuf, fsize, psize_offset, payload_offset);
    if (psize == 0) {
        printf("ERROR: invalid payload size!");
        free(inbuf);
        free(outbuf);
        return -1;
    }

    memcpy(inbuf, outbuf + payload_offset, fsize - payload_offset);
    if (!decode_stage2(inbuf, outbuf,  (fsize - payload_offset))) {
        free(inbuf);
        free(outbuf);
        return -1;
    }

    if (is_header_valid(outbuf, psize)) {
        printf("Success! New executable found!\n");
    } else {
        printf("Decoded content is invalid!\n");
    }
    dump_to_file(outbuf, psize, argv[2]);
    free(inbuf);
    free(outbuf);
    return 0;
}
